using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using NSubstitute;
using NSubstitute.Core;
using Phoenix.Frames;
using Phoenix.Framework;
using Xunit;

namespace Phoenix.Tests
{
    public static class BuilderAssertHelpers
    {
        public static void CommandInvokes(this ICommand command, ControllerActionContext context,
                                          string controllerAction, IContentFrame customFrame = null, object[] parameters = null, InvokeOptions options = InvokeOptions.None)
        {
            command.Execute(null);

            VerifyCall(context, controllerAction, "InvokeAction", customFrame, parameters, options);
        }

        public static void CommandInvokes<T>(this ICommand command, ControllerActionContext context,
                                             string controllerAction, IContentFrame customFrame = null, object[] parameters = null, 
                                             T commandArgument = default(T), InvokeOptions options = InvokeOptions.None)
        {
            command.Execute(commandArgument);

            VerifyCall(context, controllerAction, "InvokeAction", customFrame, parameters, options);
        }

        public static void ActionInvoked(this Task invokeAction, ControllerActionContext context,
                                         string controllerAction, IContentFrame customFrame = null, object[] parameters = null,
                                         InvokeOptions options = InvokeOptions.None)
        {
            VerifyCall(context, controllerAction, "InvokeAction", customFrame, parameters, options);
        }

        public static void ActionInvokedWithResult<TResultType>(this Task invokeAction, ControllerActionContext context,
                                                                string controllerAction, IContentFrame customFrame = null, object[] parameters = null, InvokeOptions options = InvokeOptions.None)
        {
            VerifyCall(context, controllerAction, "InvokeActionWithResult", customFrame, parameters, options,
                       (call, request) => Assert.Equal(typeof(TResultType), call.GetMethodInfo().GetGenericArguments()[0]));
        }

        static void VerifyCall(ControllerActionContext context, string controllerAction, string expectedMethodName, 
                               IContentFrame customFrame, object[] parameters, InvokeOptions options, params Action<ICall, InvokeActionRequest>[] additionalAsserts)
        {
            var call = context.Host.ActionInvoker.ReceivedCalls().Single();
            var methodName = call.GetMethodInfo().Name;
            var request = (InvokeActionRequest)call.GetArguments()[0];

            var expected = ControllerAction.CreateFrom(controllerAction);

            if (customFrame != null)
                Assert.Equal(customFrame, request.TargetFrame);
            Assert.Equal(expectedMethodName, methodName);
            Assert.Equal(expected.Action, request.Target.Action);
            Assert.Equal(expected.Controller, request.Target.Controller);
            Assert.Equal(options, request.Options);

            if (parameters != null)
            {
                var passedParams = request.Parameters.Select(p => p.Value).ToArray();
                for (var i = 0; i < parameters.Length; i++)
                {
                    Assert.True(parameters[i].Equals(passedParams[i]),
                                string.Format("Parameter {0} was {1} but should be {2}", i, passedParams[i], parameters[i]));
                }
            }

            foreach (var additionalAssert in additionalAsserts)
            {
                additionalAssert(call, request);
            }

            //Reset calls
            context.Host.ActionInvoker.Returns(Substitute.For<IActionInvoker>());
        }
    }
}